use chrono::{Duration, NaiveTime};

use super::{AliasTo, BadTimeBitmapError, TimeOutOfRange, TimeRange};

lazy_static::lazy_static! {
    static ref   START_TIME: NaiveTime = {
        NaiveTime::from_hms(6, 0, 0)
    };

    static ref END_TIME:NaiveTime = {
        NaiveTime::from_hms(22, 0, 0)
    };

    static ref ONE_PIECES_SEC:i64 = {
        15 * 60
    };

    static ref ONE_PIECES_MIN:i64 = {
        15
    };
}

impl TimeRange {
    pub fn to_time(&self) -> Result<(NaiveTime, Duration), BadTimeBitmapError> {
        let (time, dur) = self.to_time_idx()?;
        let dur = Duration::minutes(*ONE_PIECES_MIN * (dur as i64));
        let start_time = START_TIME.clone();
        let (final_time, _) =
            start_time.overflowing_add_signed(Duration::minutes(time as i64 * *ONE_PIECES_MIN));

        Ok((final_time, dur))
    }

    pub fn from_time_and_duration(time: NaiveTime, dur: Duration) -> Result<Self, TimeOutOfRange> {
        let start = time;
        let start = Self::time_to_id(start, AliasTo::Lower)?;

        let (end, _) = Self::id_to_time(start).overflowing_add_signed(dur);

        println!("{end}");

        let end = Self::time_to_id(end, AliasTo::Upper)?;

        Ok(Self(
            ((u64::MAX >> end) ^ u64::MAX) ^ (u64::MAX >> start) ^ u64::MAX,
        ))
    }
}

impl From<u64> for TimeRange {
    fn from(s: u64) -> Self {
        Self(s)
    }
}

impl TimeRange {
    fn to_time_idx(&self) -> Result<(u8, u8), BadTimeBitmapError> {
        let pice = self.0;
        let time_idx = pice.leading_zeros() as u8;

        let pice = pice << time_idx;
        let duration = pice.leading_ones() as u8;

        if (pice << duration) > 0 {
            return Err(BadTimeBitmapError);
        }

        Ok((time_idx, duration))
    }

    fn id_to_time(idx: u8) -> NaiveTime {
        let start_time = START_TIME.clone();
        let (final_time, _) =
            start_time.overflowing_add_signed(Duration::minutes(idx as i64 * *ONE_PIECES_MIN));

        final_time
    }

    fn time_to_id(time: NaiveTime, alias: AliasTo) -> Result<u8, TimeOutOfRange> {
        if &*START_TIME <= &time && &time <= &*END_TIME {
            let min = time.signed_duration_since(START_TIME.clone()).num_minutes();
            let min = min as u64;
            let idx = min / 15;
            let remain = min % 15;

            let idx = idx as u8;
            let idx = match (alias, remain) {
                (AliasTo::Lower, _) => idx,
                (AliasTo::Upper, 0) => idx,
                _ => idx + 1,
            };
            Ok(idx)
        } else {
            Err(TimeOutOfRange)
        }
    }
}

#[cfg(test)]
mod tset {

    use chrono::{Duration, NaiveTime};

    use super::TimeRange;
    static TIME: u64 =
        0b_0000_1111_1111_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000;
    #[test]
    fn tset_u642time() {
        let t = TimeRange(TIME);

        let (t, d) = t.to_time_idx().unwrap();

        assert_eq!(t, 4);
        assert_eq!(d, 8);
    }

    #[test]
    fn tset_u64_to_time_light() {
        let t = TimeRange(TIME);

        let (t, d) = t.to_time().unwrap();

        assert_eq!(t, NaiveTime::from_hms(7, 0, 0));
        assert_eq!(d, Duration::minutes(8 * 15))
    }

    #[test]
    fn time_to_id() {
        let time = NaiveTime::from_hms(7, 35, 0);
        assert_eq!(
            TimeRange::time_to_id(time, crate::utils::time_using::AliasTo::Lower).unwrap(),
            6
        );
        let time = NaiveTime::from_hms(7, 29, 0);
        assert_eq!(
            TimeRange::time_to_id(time, crate::utils::time_using::AliasTo::Lower).unwrap(),
            5
        );
        let time = NaiveTime::from_hms(18, 11, 12);
        assert_eq!(
            TimeRange::time_to_id(time, crate::utils::time_using::AliasTo::Lower).unwrap(),
            48
        );
    }
    #[test]
    fn time_to_use() {
        let time = NaiveTime::from_hms(7, 11, 0);
        let dur = Duration::minutes(7 * 15 + 1);

        let v = TimeRange::from_time_and_duration(time, dur).unwrap();

        assert_eq!(v.0, TIME, "bit map error l: {:064b}, r: {:064b}", v.0, TIME);
    }
}
